package com.dyz.wsw.security.aspect;

import com.dyz.wsw.common.consts.web.SecurityConst;
import com.dyz.wsw.common.enumerator.ServiceCode;
import com.dyz.wsw.common.ex.ServiceException;
import com.dyz.wsw.common.pojo.auth.Principal;
import com.dyz.wsw.common.web.JsonResult;
import com.dyz.wsw.security.annotation.Authentication;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.lang.reflect.Parameter;

/**
 * @author dyz
 * @date 2025/2/16 0:00
 */
@Aspect
@Slf4j
@Component
public class WswSecurityAuthAspect implements SecurityConst {

    public WswSecurityAuthAspect(){
        log.debug("创建认证切面对象: WswSecurityAuthAspect");
    }


    /**
     * execution切点指示器,用于匹配方法执行的连接点【当某个方法被执行时，如果满足这个表达式的条件就会触发对应的切面逻辑】
     * 第一个'*'表示任意的返回类型，无论目标方法返回什么类型都可以匹配到
     * 第二个'*'用于匹配任意的方法名。无论目标方法叫什么名字，都有可能被匹配
     * '..'表示任意数量的任意类型参数，在参数列表的开头和结尾使用'..'表示这个特定参数前后可以有任意数量的其他参数
     * ‘@com.dyz.wsw.security.annotation.Authentication’注解匹配，表示目标方法必须有一个参数被此注解标注
     * ‘(*)’表示被注解标注的参数可以是任意类型
     * @param joinPoint 切入点
     * @return 切面结果
     */
    @Around("execution(* *(.., @com.dyz.wsw.security.annotation.Authentication (*), ..))")
    public Mono<JsonResult> aroundAuthPrincipal(ProceedingJoinPoint joinPoint){
        return Mono.deferContextual(ctv->{
            Principal ctvPrincipal = ctv.getOrDefault(PRINCIPAL, null);
            if(ctvPrincipal == null){
                throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED,"no principal found");
            }
            Integer index = getAnnotationIndex(joinPoint);
            Principal principal = (Principal)joinPoint.getArgs()[index];
            principal.setId(ctvPrincipal.getId());
            principal.setUsername(ctvPrincipal.getUsername());

            Mono<JsonResult> result;
            try {
                result = ((Mono<JsonResult>) joinPoint.proceed());

            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
            return result;
        });
    }

    /**
     * 获取注解下标
     * @param joinPoint 切入点
     * @return 被@AuthPrincipal标注的方法参数下标
     */
    private Integer getAnnotationIndex(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Parameter[] parameters = signature.getMethod().getParameters();
        for (int i = 0; i < parameters.length; i++) {
            if(parameters[i].isAnnotationPresent(Authentication.class)){
                return i;
            }
        }
        throw new ServiceException(ServiceCode.ERROR_UNAUTHORIZED,"No @AuthPrincipal index found");
    }
}
